/*
 * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package org.eclipse.imagen;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import org.eclipse.imagen.util.CaselessStringKey;

/**
 * A class to manage the descriptors belong to a certain <code>RegistryMode</code> The <code>RegistryElementDescriptor
 * </code> names are used in a case-insensitive manner.
 */
class DescriptorCache {

    /** The name of the mode for which the cache of descriptors is being maintained. */
    final String modeName;

    /** Cache the RegistryMode since it is bound to get used many, many times. */
    final RegistryMode mode;

    /** Does the registry mode for this cache support preferences. */
    final boolean arePreferencesSupported;

    /** Does the registry mode for this cache support properties. */
    final boolean arePropertiesSupported;

    /** A Hashtable of all the <code>RegistryElementDescriptor</code>s, hashed by their name. */
    private Hashtable descriptorNames;

    /**
     * A Hashtable of all the products, hashed by the name of the <code>RegistryElementDescriptor</code> to which they
     * belong.
     */
    private Hashtable products;

    /**
     * A Hashtable of all the product preferences, hashed by the descriptor name that the products belong to. The
     * product preferences are stored in a <code>Vector</code>. The elements of the <code>Vector</code> consist of
     * <code>String[2]</code> objects, each storing the preferred product's name, and the other product's name within
     * it.
     */
    private Hashtable productPrefs;

    //
    // Property related tables...
    //

    /**
     * A Hashtable of <code>Vector</code>s containing all the <code>PropertyGenerator</code>s, hashed by the descriptor
     * name that the <code>PropertyGenerator</code>s belong to.
     */
    private Hashtable properties;

    /**
     * A Hashtable of <code>Vector</code>s containing the names of all the properties to be suppressed, hashed by the
     * descriptor name whose properties are to be suppressed.
     */
    private Hashtable suppressed;

    /**
     * A Hashtable containing information about which source a property should be copied from, hashed by the descriptor
     * name to which the property belongs. The information about which source a property should be copied from is stored
     * in a Hashtable containing the index of the source, hashed by the property name.
     */
    private Hashtable sourceForProp;

    /**
     * A Hashtable that stores the <code>PropertySource</code>s for all the properties, hashed by the descriptor name
     * that the properties belong to. The <code>PropertySource</code>s are stored in a Hashtable, hashed by the name of
     * the property.
     */
    private Hashtable propNames;

    /**
     * The Constructor. Create a <code>RegistryElementDescriptor</code> cache for maintaining descriptors for the
     * specified mode.
     *
     * @param modeName the registry mode name.
     */
    DescriptorCache(String modeName) {

        this.modeName = modeName;
        this.mode = RegistryMode.getMode(modeName);

        arePreferencesSupported = mode.arePreferencesSupported();
        arePropertiesSupported = mode.arePropertiesSupported();

        descriptorNames = new Hashtable();
        products = new Hashtable();

        if (arePreferencesSupported) productPrefs = new Hashtable();

        // Property related tables.
        properties = new Hashtable();
        suppressed = new Hashtable();
        sourceForProp = new Hashtable();
        propNames = new Hashtable();
    }

    /**
     * Adds a <code>RegistryElementDescriptor</code> to the cache.
     *
     * <p>An <code>RegistryElementDescriptor</code> cannot be added against a descriptor name under which another <code>
     * RegistryElementDescriptor</code> was added previously.
     *
     * @param rdesc an <code>RegistryElementDescriptor</code> containing information about the descriptor.
     * @return false, if one already existed. true otherwise.
     * @throws IllegalArgumentException is rdesc in null
     * @throws IllegalArgumentException if the descriptor has already been registered in this cache.
     */
    boolean addDescriptor(RegistryElementDescriptor rdesc) {

        if (rdesc == null) {
            throw new IllegalArgumentException(JaiI18N.getString("Generic0"));
        }

        String descriptorName = rdesc.getName();

        // Use a caseless version of the key.
        CaselessStringKey key = new CaselessStringKey(descriptorName);

        // If the key has already been added bail out ...
        if (descriptorNames.containsKey(key) == true) {
            throw new IllegalArgumentException(
                    JaiI18N.formatMsg("DescriptorCache0", new Object[] {descriptorName, modeName}));
        }

        // Store the RegistryElementDescriptor hashed by its global name
        descriptorNames.put(key, rdesc);

        // Store the ProductOperationGraph hashed by the caseless
        // descriptor name
        if (arePreferencesSupported) products.put(key, new ProductOperationGraph());

        // if properties arent supported by this descriptor we are done.
        if (rdesc.arePropertiesSupported() == false) return true;

        // Store the Property Generators associated with this descriptor
        // for the specified mode.
        PropertyGenerator props[] = rdesc.getPropertyGenerators(modeName);

        if (props != null) {
            for (int i = 0; i < props.length; i++) {

                Vector v = (Vector) properties.get(key);
                if (v == null) {
                    v = new Vector();
                    v.addElement(props[i]);
                    properties.put(key, v);
                } else {
                    v.addElement(props[i]);
                }

                v = (Vector) suppressed.get(key);
                Hashtable h = (Hashtable) sourceForProp.get(key);
                String names[] = props[i].getPropertyNames();

                for (int j = 0; j < names.length; j++) {
                    CaselessStringKey name = new CaselessStringKey(names[j]);

                    if (v != null) v.remove(name);
                    if (h != null) h.remove(name);
                }
            }
        }

        return true;
    }

    /**
     * Removes a <code>RegistryElementDescriptor</code> from the cache.
     *
     * @param descriptorName the descriptor name as a String.
     * @return false, if one wasnt previously registered, true otherwise.
     * @throws IllegalArgumentException if descriptorName is null or was not previously registered.
     * @throws IllegalArgumentException if any of the <code>PropertyGenerator</code>s associated with the <code>
     *     RegistryElementDescriptor</code> to be removed is null.
     */
    boolean removeDescriptor(String descriptorName) {

        // Use a caseless version of the key.
        CaselessStringKey key = new CaselessStringKey(descriptorName);

        // If it is not present in the cache already, then return false.
        if (descriptorNames.containsKey(key) == false) {
            throw new IllegalArgumentException(
                    JaiI18N.formatMsg("DescriptorCache1", new Object[] {descriptorName, modeName}));
        }

        RegistryElementDescriptor rdesc = (RegistryElementDescriptor) descriptorNames.get(key);

        PropertyGenerator props[] = null;

        // if properties arent supported by this descriptor we are done.
        if (rdesc.arePropertiesSupported() == true) props = rdesc.getPropertyGenerators(modeName);

        // Remove the Property Generators associated with this descriptor
        if (props != null) {
            for (int i = 0; i < props.length; i++) {

                if (props[i] == null) {
                    throw new IllegalArgumentException(JaiI18N.formatMsg(
                            "DescriptorCache2", new Object[] {new Integer(i), descriptorName, modeName}));
                }

                Vector v = (Vector) properties.get(key);
                if (v != null) {
                    v.removeElement(props[i]);
                }
            }
        }

        // Remove the RegistryElementDescriptor hashed by its global name
        descriptorNames.remove(key);

        if (arePreferencesSupported) products.remove(key);

        return true;
    }

    /**
     * Removes a <code>RegistryElementDescriptor</code> from the cache.
     *
     * @param rdesc an <code>RegistryElementDescriptor</code> to be removed.
     * @return false, if one wasnt previously registered, true otherwise.
     * @throws IllegalArgumentException if rdesc is null.
     * @throws IllegalArgumentException if rdesc was not previously registered.
     * @throws IllegalArgumentException if any of the <code>PropertyGenerator</code>s associated with the <code>
     *     RegistryElementDescriptor</code> to be removed is null.
     */
    boolean removeDescriptor(RegistryElementDescriptor rdesc) {
        if (rdesc == null) {
            throw new IllegalArgumentException(JaiI18N.getString("Generic0"));
        }
        return removeDescriptor(rdesc.getName());
    }

    /**
     * Returns the <code>RegistryElementDescriptor</code> that is currently registered under the given name, or null if
     * none exists.
     *
     * @param descriptorName the String to be queried.
     * @return an <code>RegistryElementDescriptor</code>.
     * @throws IllegalArgumentException if descriptorName is null
     */
    RegistryElementDescriptor getDescriptor(String descriptorName) {
        // Use a caseless version of the key.
        CaselessStringKey key = new CaselessStringKey(descriptorName);

        return (RegistryElementDescriptor) descriptorNames.get(key);
    }

    /**
     * Returns a <code>List</code> of all currently registered <code>RegistryElementDescriptor</code>s.
     *
     * @return a List of <code>RegistryElementDescriptor</code>s.
     */
    List getDescriptors() {

        ArrayList list = new ArrayList();

        for (Enumeration en = descriptorNames.elements(); en.hasMoreElements(); ) {
            list.add(en.nextElement());
        }

        return list;
    }

    /**
     * Returns a list of names under which all the <code>RegistryElementDescriptor</code>s in the registry are
     * registered.
     *
     * @return a list of currently existing descriptor names.
     */
    String[] getDescriptorNames() {

        Enumeration e = descriptorNames.keys();
        int size = descriptorNames.size();
        String names[] = new String[size];

        for (int i = 0; i < size; i++) {
            CaselessStringKey key = (CaselessStringKey) e.nextElement();
            names[i] = key.getName();
        }

        return names;
    }

    /**
     * Registers a product name against a descriptor. The descriptor must already exist in the cache. If the product
     * already existed under the descriptor, the old one is returned without adding another.
     *
     * @param descriptorName the descriptor name as a String
     * @param productName the product name as a String.
     * @return null, if the descriptor or the product did not exist or the product
     * @throws IllegalArgumentException if descriptorName is null
     */
    OperationGraph addProduct(String descriptorName, String productName) {
        // Use a caseless version of the key.
        CaselessStringKey key = new CaselessStringKey(descriptorName);

        if (productName == null) throw new IllegalArgumentException(JaiI18N.getString("Generic0"));

        ProductOperationGraph pog = (ProductOperationGraph) products.get(key);

        if (pog == null) return null;

        PartialOrderNode pon = pog.lookupOp(productName);

        if (pon == null) {
            pog.addProduct(productName);

            pon = pog.lookupOp(productName);
        }

        return (OperationGraph) pon.getData();
    }

    /**
     * Unregisters a product name against a descriptor. The descriptor must already exist in the cache and the procduct
     * must have been registered against the descriptor
     *
     * @param descriptorName the descriptor name as a String
     * @param productName the product name as a String.
     * @return false, if the descriptor did not exist or the product was not already registered.
     * @throws IllegalArgumentException if descriptorName is null
     */
    boolean removeProduct(String descriptorName, String productName) {
        // Use a caseless version of the key.
        CaselessStringKey key = new CaselessStringKey(descriptorName);

        if (productName == null) throw new IllegalArgumentException(JaiI18N.getString("Generic0"));

        ProductOperationGraph pog = (ProductOperationGraph) products.get(key);

        if (pog == null) return false;

        PartialOrderNode pon = pog.lookupOp(productName);

        if (pon == null) return false;

        pog.removeOp(productName);

        return true;
    }

    /**
     * Looks up a product name against a descriptor.
     *
     * @param descriptorName the descriptor name as a String
     * @param productName the product name as a String.
     * @return null, if the descriptor did not exist or the product was not already registered. Otherwise returns the
     *     <code>PartialOrderNode</code> corresponding to this product
     * @throws IllegalArgumentException if descriptorName is null
     */
    OperationGraph lookupProduct(String descriptorName, String productName) {
        // Use a caseless version of the key.
        CaselessStringKey key = new CaselessStringKey(descriptorName);

        if (productName == null) throw new IllegalArgumentException(JaiI18N.getString("Generic0"));

        ProductOperationGraph pog = (ProductOperationGraph) products.get(key);

        if (pog == null) return null;

        PartialOrderNode pon = pog.lookupOp(productName);

        if (pon == null) return null;

        return (OperationGraph) pon.getData();
    }

    /**
     * Sets a preference between two products registered under a common <code>RegistryElementDescriptor</code>. if the
     * descriptor was not registered previously and no preference will be set. Any attempt to set a preference between a
     * product and itself will be ignored.
     *
     * @param descriptorName the operation name as a String.
     * @param preferredProductName the product to be preferred.
     * @param otherProductName the other product.
     * @return false, if the descriptor was not registered previously or if either if the products were not already
     *     added against the descriptor.
     * @throws IllegalArgumentException if this registry mode does not support preferences.
     * @throws IllegalArgumentException if any of the args is null
     * @throws IllegalArgumentException if descriptorName or either of the products were not previously registered.
     */
    boolean setProductPreference(String descriptorName, String preferredProductName, String otherProductName) {

        if (!arePreferencesSupported) {
            throw new IllegalArgumentException(JaiI18N.formatMsg("DescriptorCache6", new Object[] {modeName}));
        }

        if ((descriptorName == null) || (preferredProductName == null) || (otherProductName == null))
            throw new IllegalArgumentException(JaiI18N.getString("Generic0"));

        // Attempt to set preference of a product with itself, do nothing.
        if (preferredProductName.equalsIgnoreCase(otherProductName)) {
            return false;
        }

        // Use a caseless version of the key.
        CaselessStringKey key = new CaselessStringKey(descriptorName);

        if (descriptorNames.containsKey(key) == false) {
            throw new IllegalArgumentException(
                    JaiI18N.formatMsg("DescriptorCache1", new Object[] {descriptorName, modeName}));
        }

        ProductOperationGraph og = (ProductOperationGraph) products.get(key);

        if (og == null) {
            throw new IllegalArgumentException(
                    JaiI18N.formatMsg("DescriptorCache3", new Object[] {descriptorName, modeName}));
        }

        if (og.lookupOp(preferredProductName) == null) {
            throw new IllegalArgumentException(JaiI18N.formatMsg(
                    "DescriptorCache4", new Object[] {descriptorName, modeName, preferredProductName}));
        }

        if (og.lookupOp(otherProductName) == null) {
            throw new IllegalArgumentException(
                    JaiI18N.formatMsg("DescriptorCache4", new Object[] {descriptorName, modeName, otherProductName}));
        }

        og.setPreference(preferredProductName, otherProductName);

        String[] prefs = {preferredProductName, otherProductName};

        // Update structures to reflect this new product preference.
        if (productPrefs.containsKey(key) == false) {
            Vector v = new Vector();
            v.addElement(prefs);

            productPrefs.put(key, v);

        } else {
            Vector v = (Vector) productPrefs.get(key);
            v.addElement(prefs);
        }

        return true;
    }

    /**
     * Removes a preference between two products registered under a common <code>RegistryElementDescriptor</code>. An
     * error message will be printed out if the operation was not registered previously.
     *
     * @param descriptorName the operation name as a String.
     * @param preferredProductName the product formerly preferred.
     * @param otherProductName the other product.
     * @return false, if the descriptor was not registered previously or if either if the products were not already
     *     added against the descriptor.
     * @throws IllegalArgumentException if this registry mode does not support preferences.
     * @throws IllegalArgumentException if any of the args is null
     * @throws IllegalArgumentException if descriptorName or either of the products were not previously registered.
     */
    boolean unsetProductPreference(String descriptorName, String preferredProductName, String otherProductName) {

        if (!arePreferencesSupported) {
            throw new IllegalArgumentException(JaiI18N.formatMsg("DescriptorCache6", new Object[] {modeName}));
        }

        if ((descriptorName == null) || (preferredProductName == null) || (otherProductName == null))
            throw new IllegalArgumentException(JaiI18N.getString("Generic0"));

        // Attempt to unset preference of a product with itself, do nothing.
        if (preferredProductName.equalsIgnoreCase(otherProductName)) {
            return false;
        }

        // Use a caseless version of the key.
        CaselessStringKey key = new CaselessStringKey(descriptorName);

        if (descriptorNames.containsKey(key) == false) {
            throw new IllegalArgumentException(
                    JaiI18N.formatMsg("DescriptorCache1", new Object[] {descriptorName, modeName}));
        }

        ProductOperationGraph og = (ProductOperationGraph) products.get(key);

        if (og == null) {
            throw new IllegalArgumentException(
                    JaiI18N.formatMsg("DescriptorCache3", new Object[] {descriptorName, modeName}));
        }

        if (og.lookupOp(preferredProductName) == null) {
            throw new IllegalArgumentException(JaiI18N.formatMsg(
                    "DescriptorCache4", new Object[] {descriptorName, modeName, preferredProductName}));
        }

        if (og.lookupOp(otherProductName) == null) {
            throw new IllegalArgumentException(
                    JaiI18N.formatMsg("DescriptorCache4", new Object[] {descriptorName, modeName, otherProductName}));
        }

        og.unsetPreference(preferredProductName, otherProductName);

        // Update structures to reflect removal of this product preference.
        if (productPrefs.containsKey(key) == false) {
            throw new IllegalArgumentException(
                    JaiI18N.formatMsg("DescriptorCache5", new Object[] {descriptorName, modeName}));
        }

        Vector v = (Vector) productPrefs.get(key);
        Iterator it = v.iterator();
        while (it.hasNext()) {
            String[] prefs = (String[]) it.next();

            if (prefs[0].equalsIgnoreCase(preferredProductName) && prefs[1].equalsIgnoreCase(otherProductName)) {
                it.remove();
                break;
            }
        }

        return true;
    }

    /**
     * Removes all preferences between products registered under a common <code>RegistryElementDescriptor</code>. An
     * error message will be printed out if the operation was not registered previously.
     *
     * @param descriptorName the operation name as a String.
     * @throws IllegalArgumentException if this registry mode does not support preferences.
     * @throws IllegalArgumentException if descriptorName is null.
     */
    boolean clearProductPreferences(String descriptorName) {

        if (!arePreferencesSupported) {
            throw new IllegalArgumentException(JaiI18N.formatMsg("DescriptorCache6", new Object[] {modeName}));
        }

        // Use a caseless version of the key.
        CaselessStringKey key = new CaselessStringKey(descriptorName);

        if (descriptorNames.containsKey(key) == false) {
            throw new IllegalArgumentException(
                    JaiI18N.formatMsg("DescriptorCache1", new Object[] {descriptorName, modeName}));
        }

        ProductOperationGraph og = (ProductOperationGraph) products.get(key);

        if (og == null) {
            throw new IllegalArgumentException(
                    JaiI18N.formatMsg("DescriptorCache3", new Object[] {descriptorName, modeName}));
        }

        // if there are no preferences to clear..
        if (productPrefs.containsKey(key) == false) return true;

        Vector v = (Vector) productPrefs.get(key);
        Enumeration e = v.elements();

        while (e.hasMoreElements()) {
            String prefs[] = (String[]) e.nextElement();

            String pref = prefs[0];
            String other = prefs[1];

            if (og.lookupOp(pref) == null) {
                throw new IllegalArgumentException(
                        JaiI18N.formatMsg("DescriptorCache4", new Object[] {descriptorName, modeName, pref}));
            }

            if (og.lookupOp(other) == null) {
                throw new IllegalArgumentException(
                        JaiI18N.formatMsg("DescriptorCache4", new Object[] {descriptorName, modeName, other}));
            }

            og.unsetPreference(pref, other);
        }
        productPrefs.remove(key);
        return true;
    }

    /**
     * Returns a list of the pairwise product preferences under a particular <code>RegistryElementDescriptor</code>. If
     * no product preferences have been set, returns null.
     *
     * @param descriptorName the operation name as a String.
     * @return an array of 2-element arrays of Strings.
     * @throws IllegalArgumentException if this registry mode does not support preferences.
     * @throws IllegalArgumentException if descriptorName is null
     */
    String[][] getProductPreferences(String descriptorName) {

        if (!arePreferencesSupported) {
            throw new IllegalArgumentException(JaiI18N.formatMsg("DescriptorCache6", new Object[] {modeName}));
        }

        // Use a caseless version of the key.
        CaselessStringKey key = new CaselessStringKey(descriptorName);

        Vector v;

        if (productPrefs.containsKey(key) == false) {
            // No product preferences have been set.
            return null;
        } else {
            v = (Vector) productPrefs.get(key);
            int s = v.size();
            if (s == 0) {
                return null;
            }
            String productPreferences[][] = new String[s][2];
            int count = 0;
            Enumeration e = v.elements();
            while (e.hasMoreElements()) {
                String[] o = (String[]) e.nextElement();
                productPreferences[count][0] = o[0];
                productPreferences[count++][1] = o[1];
            }

            return productPreferences;
        }
    }

    /**
     * Returns a list of the products registered under a particular <code>RegistryElementDescriptor</code>, in an
     * ordering that satisfies all of the pairwise preferences that have been set. Returns <code>null</code> if cycles
     * exist. Returns <code>null</code> if no <code>RegistryElementDescriptor</code> has been registered under this
     * descriptorName, or if no products exist for this operation.
     *
     * @param descriptorName the operation name as a String.
     * @return a Vector of Strings representing product names. returns null if this registry mode does not support
     *     preferences.
     * @throws IllegalArgumentException if descriptorName is null.
     */
    Vector getOrderedProductList(String descriptorName) {

        if (!arePreferencesSupported) return null;

        // Use a caseless version of the key.
        CaselessStringKey key = new CaselessStringKey(descriptorName);

        if (descriptorNames.containsKey(key) == false) {
            return null;
        }

        ProductOperationGraph productGraph = (ProductOperationGraph) products.get(key);

        // If no products exist under this Operation Name
        if (productGraph == null) {
            return null;
        }

        // Get the ordered vector of PartialOrderNodes
        Vector v1 = productGraph.getOrderedOperationList();

        if (v1 == null) return null;

        int size = v1.size();

        if (size == 0) { // no element
            return null;
        } else {
            Vector v2 = new Vector();
            for (int i = 0; i < size; i++) {
                v2.addElement(((PartialOrderNode) v1.elementAt(i)).getName());
            }

            return v2;
        }
    }

    // Property management

    private boolean arePropertiesSupported(String descriptorName) {

        CaselessStringKey key = new CaselessStringKey(descriptorName);

        RegistryElementDescriptor rdesc = (RegistryElementDescriptor) descriptorNames.get(key);

        if (rdesc == null) {
            throw new IllegalArgumentException(
                    JaiI18N.formatMsg("DescriptorCache1", new Object[] {descriptorName, modeName}));
        }

        return arePropertiesSupported;
    }

    /** Removes all property associated information from this <code>DescriptorCache</code>. */
    void clearPropertyState() {

        if (arePropertiesSupported == false) {
            throw new IllegalArgumentException(JaiI18N.formatMsg("DescriptorCache7", new Object[] {modeName}));
        }

        properties = new Hashtable();
        suppressed = new Hashtable();
    }

    /**
     * Adds a <code>PropertyGenerator</code> to the a particular descriptor.
     *
     * @param descriptorName the operation name as a String.
     * @param generator the <code>PropertyGenerator</code> to be added.
     */
    void addPropertyGenerator(String descriptorName, PropertyGenerator generator) {

        if ((descriptorName == null) || (generator == null))
            throw new IllegalArgumentException(JaiI18N.getString("Generic0"));

        if (arePropertiesSupported(descriptorName) == false) {
            throw new IllegalArgumentException(JaiI18N.formatMsg("DescriptorCache7", new Object[] {modeName}));
        }

        CaselessStringKey key = new CaselessStringKey(descriptorName);

        Vector v = (Vector) properties.get(key);

        if (v == null) {
            v = new Vector();
            properties.put(key, v);
        }

        v.addElement(generator);

        v = (Vector) suppressed.get(key);
        Hashtable h = (Hashtable) sourceForProp.get(key);

        String names[] = generator.getPropertyNames();

        for (int j = 0; j < names.length; j++) {
            CaselessStringKey name = new CaselessStringKey(names[j]);

            if (v != null) v.remove(name);
            if (h != null) h.remove(name);
        }
    }

    private void hashNames(String descriptorName) {
        CaselessStringKey key = new CaselessStringKey(descriptorName);

        Vector c = (Vector) properties.get(key);
        Vector s = (Vector) suppressed.get(key);

        Hashtable h = new Hashtable();
        propNames.put(key, h);

        if (c != null) {
            PropertyGenerator pg;
            String names[];

            for (Iterator it = c.iterator(); it.hasNext(); ) {
                pg = (PropertyGenerator) it.next();
                names = pg.getPropertyNames();

                for (int i = 0; i < names.length; i++) {
                    CaselessStringKey name = new CaselessStringKey(names[i]);

                    // Don't add a property that was suppressed
                    if ((s == null) || !s.contains(name)) {
                        h.put(name, pg);
                    }
                }
            }
        }

        Hashtable htable = (Hashtable) sourceForProp.get(key);

        if (htable != null) {
            for (Enumeration e = htable.keys(); e.hasMoreElements(); ) {
                CaselessStringKey name = (CaselessStringKey) e.nextElement();

                int i = ((Integer) htable.get(name)).intValue();

                PropertyGenerator generator = new PropertyGeneratorFromSource(i, name.getName());

                h.put(name, generator);
            }
        }
    }

    /**
     * Removes a <code>PropertyGenerator</code> from its association with a particular descriptor. If the generator was
     * not associated with the descriptor, nothing happens.
     *
     * @param descriptorName the operation name as a String.
     * @param generator the <code>PropertyGenerator</code> to be removed.
     * @throws IllegalArgumentException if descriptorName is null.
     * @throws IllegalArgumentException if generator is null.
     */
    void removePropertyGenerator(String descriptorName, PropertyGenerator generator) {

        if ((descriptorName == null) || (generator == null)) {
            throw new IllegalArgumentException(JaiI18N.getString("Generic0"));
        }

        if (arePropertiesSupported(descriptorName) == false) {
            throw new IllegalArgumentException(JaiI18N.formatMsg("DescriptorCache7", new Object[] {modeName}));
        }

        CaselessStringKey key = new CaselessStringKey(descriptorName);

        Vector v = (Vector) properties.get(key);

        if (v != null) {
            v.removeElement(generator);
        }
    }

    /**
     * Forces a particular property to be suppressed by nodes associated with a particular descriptor. By default,
     * properties are passed through unchanged.
     *
     * @param descriptorName the operation name as a String.
     * @param propertyName the name of the property to be suppressed.
     * @throws IllegalArgumentException if descriptorName is null.
     * @throws IllegalArgumentException if propertyName is null.
     */
    void suppressProperty(String descriptorName, String propertyName) {

        if ((descriptorName == null) || (propertyName == null)) {
            throw new IllegalArgumentException(JaiI18N.getString("Generic0"));
        }

        if (arePropertiesSupported(descriptorName) == false) {
            throw new IllegalArgumentException(JaiI18N.formatMsg("DescriptorCache7", new Object[] {modeName}));
        }

        CaselessStringKey key = new CaselessStringKey(descriptorName);
        CaselessStringKey propertyKey = new CaselessStringKey(propertyName);

        // Mark the property name as suppressed.
        Vector v = (Vector) suppressed.get(key);

        if (v == null) {
            v = new Vector();
            suppressed.put(key, v);
        }

        v.addElement(propertyKey);

        Hashtable h = (Hashtable) sourceForProp.get(key);

        if (h != null) {
            h.remove(propertyKey);
        }
    }

    /**
     * Forces all properties to be suppressed by nodes associated with a particular descriptor. By default, properties
     * are passed through unchanged.
     *
     * @param descriptorName the operation name as a String.
     * @throws IllegalArgumentException if descriptorName is null.
     */
    void suppressAllProperties(String descriptorName) {

        if (arePropertiesSupported(descriptorName) == false) {
            throw new IllegalArgumentException(JaiI18N.formatMsg("DescriptorCache7", new Object[] {modeName}));
        }

        // In this method synchronized takes care of the fact that all the
        // operations take place in a sequential fashion, while
        // suppressProperty's writeLock insures that all changes are
        // made by only one thread.
        CaselessStringKey key = new CaselessStringKey(descriptorName);

        // Get names of all properties that this descriptorName
        // is associated with
        Vector v = (Vector) properties.get(key);

        if (v != null) {
            PropertyGenerator pg;

            for (Iterator it = v.iterator(); it.hasNext(); ) {
                pg = (PropertyGenerator) it.next();
                String propertyNames[] = pg.getPropertyNames();
                for (int i = 0; i < propertyNames.length; i++) {
                    suppressProperty(descriptorName, propertyNames[i]);
                }
            }
        }
    }

    /**
     * Forces a property to be copied from the specified source index by nodes associated with a particular descriptor.
     * By default, a property is copied from the first source node that emits it. The result of specifying an invalid
     * source is undefined.
     *
     * @param descriptorName the operation name as a String.
     * @param propertyName the name of the property to be copied.
     * @param sourceIndex the index of the source to copy the property from.
     * @throws IllegalArgumentException if descriptorName is null.
     * @throws IllegalArgumentException if propertyName is null.
     */
    void copyPropertyFromSource(String descriptorName, String propertyName, int sourceIndex) {

        if ((descriptorName == null) || (propertyName == null)) {
            throw new IllegalArgumentException(JaiI18N.getString("Generic0"));
        }

        if (arePropertiesSupported(descriptorName) == false) {
            throw new IllegalArgumentException(JaiI18N.formatMsg("DescriptorCache7", new Object[] {modeName}));
        }

        CaselessStringKey key = new CaselessStringKey(descriptorName);
        CaselessStringKey propertyKey = new CaselessStringKey(propertyName);

        Hashtable h = (Hashtable) sourceForProp.get(key);

        if (h == null) {
            h = new Hashtable();
            sourceForProp.put(key, h);
        }

        h.put(propertyKey, new Integer(sourceIndex));

        Vector v = (Vector) suppressed.get(key);

        if (v != null) {
            v.remove(propertyKey);
        }
    }

    /**
     * Returns a list of the properties generated by nodes implementing the functionality associated with a particular
     * descriptor. Returns null if no properties are generated.
     *
     * @param descriptorName the operation name as a String.
     * @return an array of Strings.
     * @throws IllegalArgumentException if descriptorName is null.
     */
    String[] getGeneratedPropertyNames(String descriptorName) {

        if (arePropertiesSupported(descriptorName) == false) {
            throw new IllegalArgumentException(JaiI18N.formatMsg("DescriptorCache7", new Object[] {modeName}));
        }

        CaselessStringKey key = new CaselessStringKey(descriptorName);

        hashNames(descriptorName);

        Hashtable h = (Hashtable) propNames.get(key);

        if (h != null && h.size() > 0) {
            String names[] = new String[h.size()];
            int count = 0;
            for (Enumeration e = h.keys(); e.hasMoreElements(); ) {
                CaselessStringKey str = (CaselessStringKey) e.nextElement();
                names[count++] = str.getName();
            }

            return count > 0 ? names : null;
        }

        return null;
    }

    /**
     * Merge mode-specific property environment with mode-independent property environment of the descriptor. Array
     * elements of "sources" are expected to be in the same ordering as referenced by the "sourceIndex" parameter of
     * copyPropertyFromSource().
     *
     * @param descriptorName the descriptor name as a <code>String</code>
     * @param sources the <code>PropertySource</code>s corresponding to the sources of the object representing the named
     *     descriptor in the indicated mode.
     * @param op the <code>Object</code> from which properties will be generated.
     * @return A <code>PropertySource</code> which encapsulates the global property environment for the object
     *     representing the named descriptor in the indicated mode.
     * @throws IllegalArgumentException if any of the arguments is <code>null</code>
     * @throws IllegalArgumentException if there is no <code>
     *             RegistryElementDescriptor</code> registered against the <code>descriptorName</code>
     * @throws IllegalArgumentException if the specified mode does not support properties.
     * @since JAI 1.1
     */
    PropertySource getPropertySource(String descriptorName, Object op, Vector sources) {

        if ((descriptorName == null) || (op == null) || (sources == null)) {
            throw new IllegalArgumentException(JaiI18N.getString("Generic0"));
        }

        if (arePropertiesSupported(descriptorName) == false) {
            throw new IllegalArgumentException(JaiI18N.formatMsg("DescriptorCache7", new Object[] {modeName}));
        }

        CaselessStringKey key = new CaselessStringKey(descriptorName);

        Vector pg = (Vector) properties.get(key);
        Vector sp = (Vector) suppressed.get(key);

        Hashtable sfp = (Hashtable) sourceForProp.get(key);

        return new PropertyEnvironment(sources, pg, sp, sfp, op);
    }
}
